package com.youlin.security;

import com.youlin.dto.BaseDataResDto;
import com.youlin.dto.BaseResDto;
import com.youlin.dto.Status;
import com.youlin.dto.res.AdminResDto;
import com.youlin.dto.res.RoleResDto;
import com.youlin.entities.BygAdmin;
import com.youlin.service.BygAdminService;
import com.youlin.untils.AdminUtil;
import com.youlin.untils.BaseService;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 *  Spring Security默认是禁用注解的，要想开启注解，
 *  需要在继承WebSecurityConfigurerAdapter的类上加@EnableGlobalMethodSecurity注解，
 *  来判断用户对某个控制层的方法是否具有访问权限
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);

    @Autowired
    BygAdminService userService;
    @Autowired
    MyMetadataSource myMetadataSource;
    @Autowired
    MyAccessDecisionManager myAccessDecisionManager;
    @Autowired
    private SessionRegistry sessionRegistry;
    @Autowired
    AjaxSessionInformationExpiredStrategy ajaxSessionInformationExpiredStrategy;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService)
            .passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    public void configure(WebSecurity web) {
//        web.ignoring().antMatchers("/**/guest/**",
//                "/doc.html","/swagger-resources", "/v2/**", "/webjars/**");
        web.ignoring().antMatchers("/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                @Override
                public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                    o.setSecurityMetadataSource(myMetadataSource);
                    o.setAccessDecisionManager(myAccessDecisionManager);
                    return o;
                }
            })
            .and()
            .formLogin()
            .loginProcessingUrl("/login")
            .usernameParameter("name")
            .passwordParameter("pwd")
            .failureHandler((HttpServletRequest req, HttpServletResponse resp, AuthenticationException e)  -> {
                BaseResDto baseResDto = new BaseResDto(Status.LOGINFAILED);
                if (e instanceof DisabledException) baseResDto = new BaseResDto(Status.USERDISABLE);
                if (e.getCause() instanceof UnitNotFoundException) baseResDto = new BaseResDto(Status.UNITCODEERROR);
                res(resp, baseResDto);
            })
            .successHandler((HttpServletRequest req, HttpServletResponse res,
                             Authentication auth) -> {
                String setCookie = res.getHeader("Set-Cookie");
                BygAdmin currentAdmin = AdminUtil.getCurrentAdmin();
                AdminUtil.onlyUser(sessionRegistry, currentAdmin.getId(), setCookie);
                AdminResDto adminResDto = BaseService.copyProperties(currentAdmin, AdminResDto.class);
                RoleResDto roleResDto = BaseService.copyProperties(currentAdmin.getRole(), RoleResDto.class);
                adminResDto.setRole(roleResDto).setCreateTime(null);
                BaseDataResDto baseResDto = new BaseDataResDto(Status.SUCCESS, "登录成功").setData(adminResDto);
                res(res, baseResDto);
            })
            .permitAll()
            .and()
            .logout()
            .logoutUrl("/logout")
            .logoutSuccessHandler((HttpServletRequest req, HttpServletResponse resp,
                                   Authentication authentication) -> res(resp, new BaseResDto(Status.SUCCESS).setMessage("注销成功")))
            .permitAll()
            .and()
            .csrf()
            .disable()
            .exceptionHandling()
            .authenticationEntryPoint((HttpServletRequest httpServletRequest, HttpServletResponse resp,
                                       AuthenticationException e) -> res(resp, new BaseResDto(Status.UNLOGIN)))
            .accessDeniedHandler((HttpServletRequest httpServletRequest, HttpServletResponse resp,
                                  AccessDeniedException e) -> res(resp, new BaseResDto(Status.PERMISSIONDENIED)))
            .and()
            .sessionManagement()
            .maximumSessions(1) // 同一个账号只能在一个地方登陆
            .sessionRegistry(sessionRegistry)
            .expiredSessionStrategy(ajaxSessionInformationExpiredStrategy); // 自定义session过期策略
    }

    public void res (HttpServletResponse res, BaseResDto baseResDto) {
        res.setContentType("application/json;charset=UTF-8");
        PrintWriter out = null;
        try {
            out = res.getWriter();
            out.write(new ObjectMapper().writeValueAsString(baseResDto));
            out.flush();
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        } finally {
            if (out != null) out.close();
        }
    }

}